package panda.record;

import static java.sql.Types.*;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import panda.query.struct.Attribute;

/**
 * Information about attributes and their names The same as the concept
 * <i>Schema</i> in database
 * 
 * @author Tian Yuan
 */
public class Schema {

	private Map<Attribute, String> nmap = new HashMap<Attribute, String>();
	private Map<String, Attribute> amap = new HashMap<String, Attribute>();
	private Map<Attribute, Integer> fieldID = new HashMap<Attribute, Integer>();
	private Map<Attribute, FieldInfo> smap = new HashMap<Attribute, FieldInfo>();

	/**
	 * 
	 */
	public Schema() {

	}

	/**
	 * A fast version of constructor
	 * 
	 * @param amap
	 * @param id
	 * @param smap
	 */
	public Schema(Map<Attribute, String> nmap, Map<String, Attribute> amap, Map<Attribute, Integer> id, Map<Attribute, FieldInfo> smap) {
		this.fieldID = id;
		this.amap = amap;
		this.smap = smap;
		this.nmap = nmap;
	}

	/**
	 * Add all attributes in <i>src</i> into this schema
	 * 
	 * @param src
	 */
	public void addAll(Schema src) {
		for (Entry<String, Attribute> e : src.amap.entrySet()) {

			if (amap.containsKey(e.getKey())) {
				int c = 1;
				while (amap.containsKey(e.getKey() + c)) c++;
				amap.put(e.getKey() + c, e.getValue());
				nmap.put(e.getValue(), e.getKey() + c);
			}
			else {
				amap.put(e.getKey(), e.getValue());
				nmap.put(e.getValue(), e.getKey());
			}
		}

		smap.putAll(src.smap);

		int orgSize = fieldID.size();
		for (Entry<Attribute, Integer> e : src.fieldID.entrySet())
			fieldID.put(e.getKey(), e.getValue() + orgSize);

		//		for (Entry<String, FieldInfo> e : src.smap.entrySet()) {
		//			int c = 1;
		//			while (smap.get(e.getKey() + c) != null) c++;
		//			smap.put(e.getKey() + c, e.getValue());
		//		}
	}

	/**
	 * Add the attribute from raw information
	 * 
	 * @param name
	 *            the name of the attribute
	 * @param type
	 *            the type of the attribute
	 * @param len
	 *            the length of the attribute
	 * @param id
	 *            the ID of the attribute
	 */
	public void addAttribute(Attribute name, int type, int len, int id) {
		FieldInfo fi = new FieldInfo(type, len);
		smap.put(name, fi);

		if (amap.containsKey(name.getAttributeName())) {
			int c = 1;
			while (amap.containsKey(name.getAttributeName() + c)) c++;
			amap.put(name.getAttributeName() + c, name);
			nmap.put(name, name.getAttributeName() + c);
		}
		else {
			amap.put(name.getAttributeName(), name);
			nmap.put(name, name.getAttributeName());
		}

		fieldID.put(name, id);
	}

	/**
	 * Add the attribute from raw information
	 * 
	 * @param name
	 *            the name of the attribute
	 * @param type
	 *            the type of the attribute
	 * @param len
	 *            the length of the attribute
	 */
	public void addAttribute(Attribute name, int type, int len) {
		addAttribute(name, type, len, fieldID.size() + 1);
	}

	/**
	 * Add the attribute with the specific name from a schema to this schema
	 * 
	 * @param attr
	 *            the name of the attribute
	 * @param sch
	 *            the schema
	 */
	public void addAttribute(Attribute attr, Schema sch) {
		//System.out.println(attr.getAttributeName());
		int t = sch.getType(attr);
		int len = sch.getLength(attr);

		addAttribute(attr, t, len);
	}

	/**
	 * Add an attribute of INTEGER type
	 * 
	 * @param attr
	 *            the name of the attribute
	 */
	public void addIntAttr(Attribute attr) {
		addAttribute(attr, INTEGER, 0);
	}
	
	public void addFloatAttr(Attribute attr) {
		addAttribute(attr, FLOAT, 0);
	}

	/**
	 * Fetch all attributes in this schema
	 * 
	 * @return
	 */
	public Collection<Attribute> getAllAttributes() {
		return smap.keySet();
	}

	/**
	 * Fetch all attribute names in this schema
	 * 
	 * @return
	 */
	public Collection<String> getAllAttributeNames() {
		return amap.keySet();
	}

	/**
	 * Determines whether there exists an attribute named attr in the schema
	 * 
	 * @param attr
	 * @return
	 */
	public boolean hasAttribute(Attribute attr) {
		return getAllAttributes().contains(attr);
	}

	/**
	 * Gets the type of the specified attribute
	 * 
	 * @param attr
	 *            the name of the attribute
	 * @return type, represented in java.sql.Types.*
	 */
	public int getType(Attribute attr) {
		return smap.get(attr).type;
	}

	/**
	 * Gets the type of the specified attribute
	 */
	public int getType(String attr) {
		return getType(amap.get(attr));
	}

	/**
	 * Gets the length of the specific attribute
	 * 
	 * @param attr
	 *            the name of the attribute
	 * @return length
	 */
	public int getLength(Attribute attr) {
		return smap.get(attr).len;
	}
	
	/**
	 * 
	 */
	public String getNameByAttribute(Attribute a) {
		return nmap.get(a);
	}

	/**
	 * 
	 * @param attr
	 * @return
	 */
	public int getLength(String attr) {
		return getLength(amap.get(attr));
	}

	public Attribute getAttributeByName(String attr) {
		return amap.get(attr);
	}

	public int IDof(String fieldName) {
		return fieldID.get(amap.get(fieldName));
	}

	public int getFieldCount() {
		return fieldID.size();
	}
}
